using System;
using System.Collections.Generic;
using FlatRedBall;
using FlatRedBall.Math;
using FlatRedBall.Gui;
using FlatRedBall.Instructions;
using FlatRedBall.ManagedSpriteGroups;
using FlatRedBall.Graphics;
using FlatRedBall.Utilities;
using System.Threading;
using FlatRedBall.Input;
using FlatRedBall.IO;
using RobotVsZombies.Screens;

namespace Rvz.Game.Main.Screens
{
    public enum AsyncLoadingState
    {
        NotStarted,
        LoadingScreen,
        Done
    }

    public class Screen
    {
        private Type _asyncScreenTypeToLoad;

        protected Camera Camera;
        protected bool IsPaused;
        protected double TimeScreenWasCreated;
        protected double AccumulatedPausedTime;

        public double PauseAdjustedCurrentTime
        {
            get { return TimeManager.CurrentTime - AccumulatedPausedTime; }
        }

        public bool ShouldRemoveLayer { get; set; }

        protected List<Screen> Popups = new List<Screen>();

        // The following are objects which belong to the screen.
        // These are removed by the Screen when it is Destroyed
        protected SpriteList Sprites = new SpriteList();
        protected List<SpriteGrid> SpriteGrids = new List<SpriteGrid>();
        protected PositionedObjectList<SpriteFrame> SpriteFrames = new PositionedObjectList<SpriteFrame>();
        protected List<IDrawableBatch> DrawableBatches = new List<IDrawableBatch>();

        // These variables control the flow from one Screen to the next.
        protected Scene LastLoadedScene;
        internal Screen NextScreenToLoadAsync;

#if !FRB_MDX
        private readonly Action _activatingAction;
        private readonly Action _deactivatingAction;
#endif

        public int ActivityCallCount { get; set; }

        public string ContentManagerName { get; private set; }

        /// <summary>
        /// Gets and sets whether the activity is finished for a particular screen.
        /// </summary>
        /// <remarks>
        /// If activity is finished, then the ScreenManager or parent
        /// screen (if the screen is a popup) knows to destroy the screen
        /// and loads the NextScreen class.</remarks>
        public bool IsActivityFinished { get; set; }
        public AsyncLoadingState AsyncLoadingState { get; private set; }
        public Layer Layer { get; set; }
        public bool ManageSpriteGrids { get; set; }

        /// <summary>
        /// The fully qualified path of the Screen-inheriting class that this screen is 
        /// to link to.
        /// </summary>
        /// <remarks>
        /// This property is read by the ScreenManager when IsActivityFinished is
        /// set to true.  Therefore, this must always be set to some value before
        /// or in the same frame as when IsActivityFinished is set to true.
        /// </remarks>
        public string NextScreen { get; set; }

        public bool IsMovingBack { get; set; }

#if !MONODROID && !FRB_MDX && !SILVERLIGHT
        public BackStackBehavior BackStackBehavior = BackStackBehavior.Move;
#endif

        protected bool UnloadsContentManagerWhenDestroyed { get; set; }

        public Screen(string contentManagerName)
        {
            ShouldRemoveLayer = true;
            UnloadsContentManagerWhenDestroyed = true;
            ContentManagerName = contentManagerName;
            ManageSpriteGrids = true;
            IsActivityFinished = false;

            Layer = ScreenManager.NextScreenLayer;

#if !FRB_MDX
            _activatingAction = new Action(Activating);
            _deactivatingAction = new Action(OnDeactivating);

            StateManager.Current.Activating += _activatingAction;
            StateManager.Current.Deactivating += _deactivatingAction;

            if (ScreenManager.ShouldActivateScreen)
            { Activating(); }
#endif
        }

#if !FRB_MDX
        public virtual void Activating()
        {
            PreActivate();// for generated code to override, to reload the statestack
            OnActivate(PlatformServices.State);// for user created code
        }


        private void OnDeactivating()
        {
            PreDeactivate();// for generated code to override, to save the statestack
            OnDeactivate(PlatformServices.State);// for user generated code;
        }

        protected virtual void OnActivate(StateManager state) { }
        protected virtual void PreActivate() { }
        protected virtual void OnDeactivate(StateManager state) { }
        protected virtual void PreDeactivate() { }
#endif

        public virtual void Activity(bool firstTimeCalled)
        {
            if (IsPaused)
            {
                AccumulatedPausedTime += TimeManager.SecondDifference;
            }

            if (ManageSpriteGrids)
            {
                foreach (SpriteGrid sg in SpriteGrids)
                { sg.Manage(); }
            }

            for (int i = Popups.Count - 1; i > -1; i--)
            {
                var popup = Popups[i];

                popup.Activity(false);
                popup.ActivityCallCount++;

                if (popup.IsActivityFinished)
                {
                    string nextPopup = popup.NextScreen;

                    popup.Destroy();
                    Popups.RemoveAt(i);

                    if (!string.IsNullOrEmpty(nextPopup))
                    {
                        LoadPopup(nextPopup, false);
                    }
                }
            }

#if !MONODROID && !FRB_MDX && !SILVERLIGHT
            // This needs to happen after popup activity
            // in case the Screen creates a popup - we don't
            // want 2 activity calls for one frame.  We also want
            // to make sure that popups have the opportunity to handle
            // back calls so that the base doesn't get it.
            if (PlatformServices.BackStackEnabled && InputManager.BackPressed && !firstTimeCalled)
            { HandleBackNavigation(); }
#endif
        }

        public void StartAsyncLoad(string screenType)
        {
            if (AsyncLoadingState == AsyncLoadingState.LoadingScreen)
            {
#if DEBUG
                throw new InvalidOperationException("This Screen is already loading a Screen of type " + _asyncScreenTypeToLoad + ".  This is a DEBUG-only exception");
#endif
            }
            if (AsyncLoadingState == AsyncLoadingState.Done)
            {
#if DEBUG
                throw new InvalidOperationException("This Screen has already loaded a Screen of type " + _asyncScreenTypeToLoad + ".  This is a DEBUG-only exception");
#endif
            }

            _asyncScreenTypeToLoad = Type.GetType(screenType);
            if (_asyncScreenTypeToLoad == null)
            { throw new Exception("Could not find the type " + screenType); }

            AsyncLoadingState = AsyncLoadingState.LoadingScreen;

            ThreadStart threadStart = PerformAsyncLoad;
            var thread = new Thread(threadStart);

            thread.Start();
        }

        private void PerformAsyncLoad()
        {
#if XBOX360
                // We can not use threads 0 or 2  
                Thread.CurrentThread.SetProcessorAffinity(4);
                mNextScreenToLoadAsync = (Screen)Activator.CreateInstance(asyncScreenTypeToLoad);
#else
            NextScreenToLoadAsync = (Screen)Activator.CreateInstance(_asyncScreenTypeToLoad, new object[0]);
#endif

            // Don't add it to the manager!
            NextScreenToLoadAsync.Initialize(false);

            AsyncLoadingState = AsyncLoadingState.Done;
        }

        public virtual void Initialize(bool addToManagers) { }

        public virtual void AddToManagers()
        {
            // We want to start the timer when we actually add to managers - this is when the activity for the Screen starts
            AccumulatedPausedTime = TimeManager.CurrentTime;
            TimeScreenWasCreated = TimeManager.CurrentTime;
        }


        public virtual void Destroy()
        {
#if !FRB_MDX
            StateManager.Current.Activating -= _activatingAction;
            StateManager.Current.Deactivating -= _deactivatingAction;
#endif
            if (LastLoadedScene != null)
            { LastLoadedScene.Clear(); }

            FlatRedBall.Debugging.Debugger.DestroyText();

            // All of the popups should be destroyed as well
            foreach (Screen s in Popups)
            { s.Destroy(); }

            SpriteManager.RemoveSpriteList(Sprites);

            // It's common for users to forget to add Particle Sprites
            // to the mSprites SpriteList.  This will either create leftover
            // particles when the next screen loads or will throw an assert when
            // the ScreenManager checks if there are any leftover Sprites.  To make
            // things easier we'll just clear the Particle Sprites here.
            var isPopup = this != ScreenManager.CurrentScreen;
            if (!isPopup)
            { SpriteManager.RemoveAllParticleSprites(); }

            // Destory all SpriteGrids that belong to this Screen
            foreach (SpriteGrid sg in SpriteGrids)
                sg.Destroy();


            // Destroy all SpriteFrames that belong to this Screen
            while (SpriteFrames.Count != 0)
            { SpriteManager.RemoveSpriteFrame(SpriteFrames[0]); }

            if (UnloadsContentManagerWhenDestroyed && ContentManagerName != FlatRedBallServices.GlobalContentManager)
            {
                FlatRedBallServices.Unload(ContentManagerName);
                FlatRedBallServices.Clean();
            }

            if (ShouldRemoveLayer && Layer != null)
            { SpriteManager.RemoveLayer(Layer); }

            if (IsPaused)
            { UnpauseThisScreen(); }

            GuiManager.Cursor.IgnoreNextClick = true;
        }

        protected virtual void PauseThisScreen()
        {
            IsPaused = true;
            InstructionManager.PauseEngine();
        }

        protected virtual void UnpauseThisScreen()
        {
            InstructionManager.UnpauseEngine();
            IsPaused = false;
        }

        public double PauseAdjustedSecondsSince(double time)
        {
            return PauseAdjustedCurrentTime - time;
        }

        /// <summary>Tells the screen that we are done and wish to move to the
        /// supplied screen</summary>
        /// <param name="screenClass">Fully Qualified Type of the screen to move to</param>
        public void MoveToScreen(string screenClass)
        {
            IsActivityFinished = true;
            NextScreen = screenClass;
        }

        public T LoadPopup<T>(Layer layerToLoadPopupOn) where T : Screen
        {
            var loadedScreen = ScreenManager.LoadScreen<T>(layerToLoadPopupOn);
            Popups.Add(loadedScreen);
            return loadedScreen;
        }

        public Screen LoadPopup(string popupToLoad, Layer layerToLoadPopupOn)
        {
            return LoadPopup(popupToLoad, layerToLoadPopupOn, true);
        }

        public Screen LoadPopup(string popupToLoad, Layer layerToLoadPopupOn, bool addToManagers)
        {
            var loadedScreen = ScreenManager.LoadScreen(popupToLoad, layerToLoadPopupOn, addToManagers, false);
            Popups.Add(loadedScreen);
            return loadedScreen;
        }

        public Screen LoadPopup(string popupToLoad, bool useNewLayer)
        {
            var loadedScreen = ScreenManager.LoadScreen(popupToLoad, useNewLayer);
            Popups.Add(loadedScreen);
            return loadedScreen;
        }

        /// <param name="state">This should be a valid enum value of the concrete screen type.</param>
        public virtual void MoveToState(int state)
        {
            // no-op
        }

        /// <summary>Default implementation tells the screen manager to finish this screen's activity and navigate
        /// to the previous screen on the backstack.</summary>
        /// <remarks>Override this method if you want to have custom behavior when the back button is pressed.</remarks>
        protected virtual void HandleBackNavigation()
        {
            // This is to prevent popups from unexpectedly going back
            if (ScreenManager.CurrentScreen == this)
            {
#if !MONODROID && !FRB_MDX && !SILVERLIGHT
                ScreenManager.NavigateBack();
#endif
            }
        }
    }
}
